package cn.demoncat.util.fastdfs;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Arrays;

import org.apache.commons.lang3.StringUtils;
import org.csource.fastdfs.ProtoCommon;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.fastdfs.config.FdProperties;
import cn.demoncat.util.fastdfs.constant.FdMsg;
import cn.demoncat.util.lang.StringUtil;

/**
 * FastDFS连接：Tracker Client
 * 
 * @author 延晓磊
 *
 * @since 2019年11月4日
 */
public class TrackerClient {

	// Tracker Server
	private final TrackerServer trackerServer;
	// 配置
	private final FdProperties fdProperties;
	
	/**
	 * Tracker Client
	 */
	public TrackerClient(TrackerServer trackerServer, FdProperties fdProperties) {
		this.trackerServer = trackerServer;
		this.fdProperties = fdProperties;
	}

	/**
	 * 获取上传文件的Storage Server
	 * 
	 * @param group		分组，为空则随机分配组
	 * 
	 * @return Storage Server
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月27日
	 */
	public StorageServer getUploadStorage(String group) {
		byte cmd;
		int out_len;
		try {
			// 获取输出流
			OutputStream out = trackerServer.getOutputStream();
			// 发送命令头
			if (StringUtils.isBlank(group)) {
				cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE;
				out_len = 0;
			} else {
				cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE;
				out_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
			}
			byte[] header = ProtoCommon.packHeader(cmd, out_len, (byte) 0);
			out.write(header);
			// 发送组信息
			if (StringUtils.isNotBlank(group)) {
				int group_len;
				byte[] bs = StringUtil.getBytes(group);
				byte[] bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
				if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
					group_len = bs.length;
				} else {
					group_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
				}
				Arrays.fill(bGroupName, (byte) 0);
				System.arraycopy(bs, 0, bGroupName, 0, group_len);
				out.write(bGroupName);
			}
			// 获取输入流
			InputStream in = trackerServer.getInputStream();
			// 读取响应
			byte[] pkgBody = ProtoCommon.recvPackage(in, ProtoCommon.TRACKER_PROTO_CMD_RESP, ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN);
			String ip_addr = new String(pkgBody, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
			int port = (int) ProtoCommon.buff2long(pkgBody, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ProtoCommon.FDFS_IPADDR_SIZE - 1);
			byte store_path = pkgBody[ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN - 1];
			// 返回StorageServer
			return new StorageServer(ip_addr, port, store_path, fdProperties);
		} catch (IOException e) {
			throw new IoRuntimeException(FdMsg.CONN_STORAGE_ERROR, e);
		}
	}

	/**
	 * 获取下载文件的Storage Server
	 * 
	 * @param group	
	 * @param filename
	 * 
	 * @return Storage Server
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public StorageServer getDownloadStorage(String group, String filename) {
		// 查询Storage Server地址
		InetSocketAddress[] servers = this.getStorages(ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE, group, filename);
		// 返回第一个
		return new StorageServer(servers[0].getHostName(), servers[0].getPort(), 0, fdProperties);
	}

	/**
	 * 获取更新文件（删除、副本、续传、截断）的Storage Server
	 * 
	 * @param group
	 * @param filename
	 * @return	Storage Server
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年5月28日
	 */
	public StorageServer getUpdateStorage(String group, String filename) {
		// 查询Storage Server地址
		InetSocketAddress[] servers = this.getStorages(ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE, group, filename);
		// 返回第一个
		return new StorageServer(servers[0].getHostName(), servers[0].getPort(), 0, fdProperties);
	}

	/**
	 * 查询Storage Server地址，用于下载、更新文件
	 * 
	 * @param cmd			命令：下载/更新（删除、副本、续传、截断）
	 * @param group
	 * @param filename
	 * 
	 * @return InetSocketAddress[]
	 */
	private InetSocketAddress[] getStorages(byte cmd, String group,String filename) {
		int len;
		try {
			// 获取输出流
			OutputStream out = trackerServer.getOutputStream();
			// 发送命令头
			byte[] bs = StringUtil.getBytes(group);
			byte[] bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
			byte[] bFileName = StringUtil.getBytes(filename);
			if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
				len = bs.length;
			} else {
				len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
			}
			Arrays.fill(bGroupName, (byte) 0);
			System.arraycopy(bs, 0, bGroupName, 0, len);
			byte[] header = ProtoCommon.packHeader(cmd, (long)ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + bFileName.length, (byte) 0);
			byte[] wholePkg = new byte[header.length + bGroupName.length + bFileName.length];
			System.arraycopy(header, 0, wholePkg, 0, header.length);
			System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
			System.arraycopy(bFileName, 0, wholePkg, header.length + bGroupName.length, bFileName.length);
			out.write(wholePkg);
			// 获取输入流
			InputStream in = trackerServer.getInputStream();
			// 读取响应
			byte[] pkgInfo = ProtoCommon.recvPackage(in, ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
			if (pkgInfo.length < ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) {
				throw new IOException("无效的响应: length = " + pkgInfo.length);
			}
			if ((pkgInfo.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) % (ProtoCommon.FDFS_IPADDR_SIZE - 1) != 0) {
				throw new IOException("无效的响应: length = " + pkgInfo.length);
			}
			int server_count = 1 + (pkgInfo.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) / (ProtoCommon.FDFS_IPADDR_SIZE - 1);
			String ip_addr = new String(pkgInfo, ProtoCommon.FDFS_GROUP_NAME_MAX_LEN, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
			int offset = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ProtoCommon.FDFS_IPADDR_SIZE - 1;
			int port = (int) ProtoCommon.buff2long(pkgInfo, offset);
			offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;
			// 返回Storage Server地址
			InetSocketAddress[] servers = new InetSocketAddress[server_count];
			servers[0] = new InetSocketAddress(ip_addr, port);
			for (int i = 1; i < server_count; i++) {
				servers[i] = new InetSocketAddress(new String(pkgInfo, offset, ProtoCommon.FDFS_IPADDR_SIZE - 1).trim(), port);
				offset += ProtoCommon.FDFS_IPADDR_SIZE - 1;
			}
			return servers;
		} catch (Exception ex) {
			throw new IoRuntimeException(FdMsg.CONN_STORAGE_ERROR, ex);
		}
	}
 
	 
	
}
